#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>

#include <psp2/io/fcntl.h>

#include "config.h"
#include "browser.h"
#include "menu.h"
#include "file.h"
#include "retro.h"
#include "utils.h"
#include "video.h"

#include "libretro.h"

SettingConfig setting_config;
MiscConfig misc_config;
GraphicsConfig graphics_config;
ControlConfig control_config;

unsigned user_language = RETRO_LANGUAGE_CHINESE_SIMPLIFIED;

char *private_assets_dir = NULL;
char *public_assets_dir = NULL;

void initConfigs()
{
    if (!public_assets_dir)
    {
        public_assets_dir = (char *)malloc(MAX_PATH_LENGTH);
        strcpy(public_assets_dir, APP_ASSETS_DIR);
    }
    APP_LOG("public_assets_dir: %s\n", public_assets_dir);
}

void trimString(char *str)
{
    int len = strlen(str);
    int i;

    for (i = len - 1; i >= 0; i--)
    {
        if (str[i] == ' ' || str[i] == '\t')
        {
            str[i] = 0;
        }
        else
        {
            break;
        }
    }
}

char *trimStringEx(char *str)
{
    char *res = str;
    while (*res == ' ' || *res == '\t')
        res++;
    trimString(res);

    return res;
}

int configGetDecimal(const char *str)
{
    return strtol(str, NULL, 0);
}

int configGetHexdecimal(const char *str)
{
    return strtoul(str, NULL, 16);
}

int configGetBoolean(const char *str)
{
    if (strcasecmp(str, "false") == 0 ||
        strcasecmp(str, "off") == 0 ||
        strcasecmp(str, "no") == 0)
        return 0;

    if (strcasecmp(str, "true") == 0 ||
        strcasecmp(str, "on") == 0 ||
        strcasecmp(str, "yes") == 0)
        return 1;

    return -1;
}

char *configGetString(const char *str)
{
    if (str[0] != '"')
        return NULL;

    char *p = strchr(str + 1, '"');
    if (!p)
        return NULL;

    int len = p - (str + 1);

    char *out = malloc(len + 1);
    strncpy(out, str + 1, len);
    out[len] = '\0';

    int i;
    for (i = 0; i < len; i++)
    {
        if (out[i] == '\\')
            out[i] = '\n';
    }

    return out;
}

int configGetLine(const char *buf, int size, char **line)
{
    uint8_t ch = 0;
    int n = 0;
    int i = 0;
    uint8_t *pbuf = (uint8_t *)buf;

    for (i = 0; i < size; i++)
    {
        ch = pbuf[i];
        if (ch < 0x20 && ch != '\t')
        {
            i++;
            break;
        }
        n++;
    }
    char *_line = NULL;
    if (n > 0)
    {
        _line = malloc(n + 1);
        strncpy(_line, buf, n);
        _line[n] = '\0';
    }
    *line = _line;

    return i;
}

int configReadLine(const char *line, char **name, char **string)
{
    int ret = 1;
    int len;

    char *_line = NULL;
    char *_name = NULL;
    char *_string = NULL;

    len = strlen(line);
    _line = malloc(len + 1);
    if (!_line)
    {
        ret = -1;
        goto END;
    }
    strcpy(_line, line);

    // Trim at beginning
    char *_pline = _line;
    while (*_pline == ' ' || *_pline == '\t')
        _pline++;

    // Ignore comments #1
    if (_pline[0] == '#')
    {
        ret = 0;
        goto END;
    }

    // Ignore comments #2
    char *p = strchr(_pline, '#');
    if (p)
    {
        // APP_LOG("IGNORE %s\n", p);
        *p = '\0';
    }

    // Get token
    p = strchr(_pline, '=');
    if (!p)
    {
        ret = -1;
        goto END;
    }

    // Name
    len = p - _pline;
    _name = malloc(len + 1);
    if (_name)
    {
        strncpy(_name, _pline, len);
        _name[len] = '\0';
        trimString(_name);
    }
    // APP_LOG("NAME: %s\n", _name);

    p++;
    while (*p == ' ' || *p == '\t')
        p++;
    trimString(p);

    // String
    len = strlen(p);
    _string = malloc(len + 1);
    if (_string)
        strcpy(_string, p);
    // APP_LOG("STRING: %s\n", _string);
    ret = 1;

END:
    *name = _name;
    *string = _string;
    if (_line)
        free(_line);

    return ret;
}

static int readEntry(const char *line, MenuEntry *entries, int n_entries)
{
    int ret = 0;

    char *name = NULL;
    char *string = NULL;
    char *value = NULL;

    ret = configReadLine(line, &name, &string);
    if (ret <= 0)
        return ret;

    value = configGetString(string);
    if (!value)
    {
        ret = 0;
        goto END;
    }

    int i, j;
    for (i = 0; i < n_entries; i++)
    {
        CoreOptionValue *option = entries[i].option;
        if (strcasecmp(name, option->key) == 0)
        {
            for (j = 0; j < option->n_options; j++)
            {
                if (strcasecmp(value, option->values[j]) == 0)
                {
                    option->pos = j;
                    ret = 1;
                    break;
                }
            }
        }
    }

END:
    if (name)
        free(name);
    if (string)
        free(string);
    if (value)
        free(value);

    return ret;
}

int readConfigBuffer(void *buffer, int size, MenuEntry *entries, int n_entries)
{
    int res = 0;
    char *line = NULL;
    char *p = buffer;

    // Skip UTF-8 bom
    uint32_t bom = 0xBFBBEF;
    if (memcmp(p, &bom, 3) == 0)
    {
        p += 3;
        size -= 3;
    }

    do
    {
        if (line)
            free(line);
        res = configGetLine(p, size, &line);

        if (res > 0)
        {
            readEntry(line, entries, n_entries);
            size -= res;
            p += res;
        }
    } while (res > 0);

    if (line)
        free(line);

    return 0;
}

int readConfig(const char *path, MenuEntry *entries, int n_entries)
{
    void *buffer = NULL;
    int size = allocateReadFile(path, &buffer);
    if (size < 0)
        return size;

    readConfigBuffer(buffer, size, entries, n_entries);

    free(buffer);

    return 0;
}

static int writeEntry(SceUID fd, MenuEntry *entry)
{
    if (!core_entries)
        return -1;

    CoreOptionValue *option = (CoreOptionValue *)entry->option;
    if (!option)
        return -1;

    int result;
    if ((result = sceIoWrite(fd, option->key, strlen(option->key))) < 0)
        return result;

    if ((result = sceIoWrite(fd, " = ", 3)) < 0)
        return result;

    char *val;
    char buffer[33];

    val = option->values[option->pos];
    sceIoWrite(fd, "\"", 1);
    result = sceIoWrite(fd, val, strlen(val));
    sceIoWrite(fd, "\"", 1);

    if (result < 0)
        return result;

    if ((sceIoWrite(fd, "\n", 1)) < 0)
        return result;

    return 0;
}

int writeConfig(const char *path, MenuEntry *entries, int n_entries)
{
    SceUID fd = sceIoOpen(path, SCE_O_WRONLY | SCE_O_CREAT | SCE_O_TRUNC, 0777);
    if (fd < 0)
        return fd;

    int i;
    for (i = 0; i < n_entries; i++)
    {
        int result = writeEntry(fd, entries + i);
        if (result != 0)
        {
            return result;
        }
    }

    sceIoClose(fd);

    return 0;
}

int setDefaultSettingConfig()
{
    memset(&setting_config, 0, sizeof(SettingConfig));
    setting_config.version = SETTING_CONFIG_VERSION;
    setting_config.preview_path = 0;
    setting_config.preview_style = 0;
    setting_config.app_log = 1;
    setting_config.core_log = 0;
#if defined(FBA_BUILD)
    setting_config.loading_log = 1;
#else
    setting_config.loading_log = 0;
#endif

    return 0;
}

int setDefaultMiscConfig()
{
    memset(&misc_config, 0, sizeof(MiscConfig));
    misc_config.version = MISC_CONFIG_VERSION;
    misc_config.auto_save_load = 1;

    return 0;
}

int setDefaultGraphicsConfig()
{
    memset(&graphics_config, 0, sizeof(GraphicsConfig));
    graphics_config.version = GRAPHICS_CONFIG_VERSION;
    graphics_config.screen_size = EMU_SCREEN_SIZE_FIT;
    graphics_config.display_rotate = 0;
    graphics_config.graphics_filtering = 0;
    graphics_config.smooth_graphics = 0;
    graphics_config.show_overlay = 1;
    graphics_config.show_fps = 0;

    return 0;
}

int setDefaultControlConfig()
{
    memset(&control_config, 0, sizeof(ControlConfig));
    control_config.version = CONTROL_CONFIG_VERSION;
    control_config.button_up = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_UP);
    control_config.button_down = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_DOWN);
    control_config.button_left = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_LEFT);
    control_config.button_right = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_RIGHT);
#if defined(FC_BUILD) || defined(GBC_BUILD) || defined(NGP_BUILD)
    control_config.button_circle = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_A);
    control_config.button_cross = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_B);
    control_config.button_triangle = SET_BUTTON_WITH_TURBO(RETRO_DEVICE_ID_JOYPAD_A);
    control_config.button_square = SET_BUTTON_WITH_TURBO(RETRO_DEVICE_ID_JOYPAD_B);
    control_config.button_l = 0;
    control_config.button_r = 0;
#elif defined(SFC_BUILD)
    control_config.button_circle = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_A);
    control_config.button_cross = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_B);
    control_config.button_triangle = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_X);
    control_config.button_square = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_Y);
    control_config.button_l = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_L);
    control_config.button_r = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_R);
#elif defined(GBA_BUILD)
    control_config.button_circle = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_A);
    control_config.button_cross = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_B);
    control_config.button_triangle = SET_BUTTON_WITH_TURBO(RETRO_DEVICE_ID_JOYPAD_A);
    control_config.button_square = SET_BUTTON_WITH_TURBO(RETRO_DEVICE_ID_JOYPAD_B);
    control_config.button_l = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_L);
    control_config.button_r = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_R);
#elif defined(MD_BUILD)
    control_config.button_circle = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_A);
    control_config.button_cross = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_B);
    control_config.button_triangle = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_X);
    control_config.button_square = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_Y);
    control_config.button_l = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_L);
    control_config.button_r = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_R);
#elif defined(WSC_BUILD)
    control_config.button_circle = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_A);
    control_config.button_cross = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_B);
    control_config.button_triangle = SET_BUTTON_WITH_TURBO(RETRO_DEVICE_ID_JOYPAD_A);
    control_config.button_square = SET_BUTTON_WITH_TURBO(RETRO_DEVICE_ID_JOYPAD_B);
    control_config.button_l = 0;
    control_config.button_r = 0;
#elif defined(PCE_BUILD)
    control_config.button_circle = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_A);
    control_config.button_cross = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_B);
    control_config.button_triangle = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_X);
    control_config.button_square = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_Y);
    control_config.button_l = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_L);
    control_config.button_r = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_R);
#elif defined(ARC_BUILD)
    control_config.button_circle = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_A);
    control_config.button_cross = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_B);
    control_config.button_triangle = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_X);
    control_config.button_square = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_Y);
    control_config.button_l = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_L);
    control_config.button_r = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_R);
#elif defined(MRP_BUILD)
    control_config.button_circle = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_A);
    control_config.button_cross = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_B);
    control_config.button_triangle = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_X);
    control_config.button_square = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_Y);
    control_config.button_l = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_L);
    control_config.button_r = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_R);
    control_config.button_l2 = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_L2);
    control_config.button_r2 = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_R2);
    control_config.button_l3 = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_L3);
    control_config.button_r3 = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_R3);
#else
    control_config.button_circle = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_A);
    control_config.button_cross = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_B);
    control_config.button_triangle = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_X);
    control_config.button_square = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_Y);
    control_config.button_l = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_L);
    control_config.button_r = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_R);
    control_config.button_l2 = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_L2);
    control_config.button_r2 = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_R2);
    control_config.button_l3 = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_L3);
    control_config.button_r3 = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_R3);
#endif
#if defined(WSC_BUILD)
    control_config.button_select = 0;
#else
    control_config.button_select = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_SELECT);
#endif
    control_config.button_start = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_START);
    control_config.left_analog = 1;
    control_config.right_analog = 0;
#if defined(PS_BUILD)
    control_config.front_touch_pad = 1;
    control_config.back_touch_pad = 1;
#else
    control_config.front_touch_pad = 0;
    control_config.back_touch_pad = 0;
#endif

    control_config.turbo_delay = 5;
#if defined(WANT_DISPLAY_ROTATE)
    graphics_config.display_rotate = 0;
    vitaVideoSetVideoNeedRefresh(1);
#endif

    return 0;
}

#if defined(WSC_BUILD)
int setDefaultControlConfigV()
{
    memset(&control_config, 0, sizeof(ControlConfig));
    control_config.version = CONTROL_CONFIG_VERSION;
    control_config.button_left = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_R2);
    control_config.button_up = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_R);
    control_config.button_right = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_L2);
    control_config.button_down = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_L);
    control_config.button_square = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_UP);
    control_config.button_triangle = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_RIGHT);
    control_config.button_circle = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_DOWN);
    control_config.button_cross = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_LEFT);
    control_config.button_l = 0;
    control_config.button_r = 0;
    control_config.button_select = 0;
    control_config.button_start = SET_BUTTON(RETRO_DEVICE_ID_JOYPAD_START);
    control_config.left_analog = 1;
    control_config.right_analog = 0;
    control_config.front_touch_pad = 0;
    control_config.back_touch_pad = 0;
    control_config.turbo_delay = 5;
    graphics_config.display_rotate = 3;

    vitaVideoSetVideoNeedRefresh(1);

    return 0;
}
#endif

static void makeConfigPath(char *path, char *config_name, int type)
{
    if (type == CONFIG_TYPE_GAME)
    {
        char name[MAX_NAME_LENGTH];
        makeCurFileName(name);
        char base_name[MAX_NAME_LENGTH];
        makeBaseName(base_name, name, MAX_NAME_LENGTH);
        snprintf(path, MAX_PATH_LENGTH, "%s/%s/%s", (CORE_CONFIGS_DIR), base_name, config_name);
    }
    else
    {
        snprintf(path, MAX_PATH_LENGTH, "%s/%s", (APP_DATA_DIR), config_name);
    }
}

int loadSettingConfig(int type)
{
    SettingConfig config;
    memset(&config, 0, sizeof(SettingConfig));

    char path[MAX_PATH_LENGTH];
    makeConfigPath(path, SETTING_CONFIG_NAME, type);

    int ret = ReadFile(path, &config, sizeof(SettingConfig));
    if (ret < 0 || ret != sizeof(SettingConfig) || config.version != SETTING_CONFIG_VERSION)
    {
        if (type == CONFIG_TYPE_MAIN)
            setDefaultSettingConfig();
        return -1;
    }

    memcpy(&setting_config, &config, sizeof(SettingConfig));

    return 0;
}

int loadMiscConfig(int type)
{
    MiscConfig config;
    memset(&config, 0, sizeof(MiscConfig));

    char path[MAX_PATH_LENGTH];
    makeConfigPath(path, MISC_CONFIG_NAME, type);

    int ret = ReadFile(path, &config, sizeof(MiscConfig));
    if (ret < 0 || ret != sizeof(MiscConfig) || config.version != MISC_CONFIG_VERSION)
    {
        if (type == CONFIG_TYPE_MAIN)
            setDefaultMiscConfig();
        return -1;
    }

    memcpy(&misc_config, &config, sizeof(MiscConfig));

    return 0;
}

int loadGraphicsConfig(int type)
{
    GraphicsConfig config;
    memset(&config, 0, sizeof(GraphicsConfig));

    char path[MAX_PATH_LENGTH];
    makeConfigPath(path, GRAPHICS_CONFIG_NAME, type);

    int ret = ReadFile(path, &config, sizeof(GraphicsConfig));
    if (ret < 0 || ret != sizeof(GraphicsConfig) || config.version != GRAPHICS_CONFIG_VERSION)
    {
        if (type == CONFIG_TYPE_MAIN)
            setDefaultGraphicsConfig();
        return -1;
    }

    memcpy(&graphics_config, &config, sizeof(GraphicsConfig));

    return 0;
}

int loadControlConfig(int type)
{
    ControlConfig config;
    memset(&config, 0, sizeof(ControlConfig));

    char path[MAX_PATH_LENGTH];
    makeConfigPath(path, CONTROL_CONFIG_NAME, type);

    int ret = ReadFile(path, &config, sizeof(ControlConfig));
    if (ret < 0 || ret != sizeof(ControlConfig) || config.version != CONTROL_CONFIG_VERSION)
    {
        if (type == CONFIG_TYPE_MAIN)
            setDefaultControlConfig();
        return -1;
    }

    memcpy(&control_config, &config, sizeof(ControlConfig));

    return 0;
}

int saveSettingConfig(int type)
{
    char path[MAX_PATH_LENGTH];
    makeConfigPath(path, SETTING_CONFIG_NAME, type);

    char parent_path[MAX_PATH_LENGTH];
    makeBaseDirectory(parent_path, path, MAX_PATH_LENGTH);
    createFolder(parent_path);

    return WriteFile(path, &setting_config, sizeof(SettingConfig));
}

int saveGraphicsConfig(int type)
{
    char path[MAX_PATH_LENGTH];
    makeConfigPath(path, GRAPHICS_CONFIG_NAME, type);

    char parent_path[MAX_PATH_LENGTH];
    makeBaseDirectory(parent_path, path, MAX_PATH_LENGTH);
    createFolder(parent_path);

    return WriteFile(path, &graphics_config, sizeof(GraphicsConfig));
}

int saveControlConfig(int type)
{
    char path[MAX_PATH_LENGTH];
    makeConfigPath(path, CONTROL_CONFIG_NAME, type);

    char parent_path[MAX_PATH_LENGTH];
    makeBaseDirectory(parent_path, path, MAX_PATH_LENGTH);
    createFolder(parent_path);

    return WriteFile(path, &control_config, sizeof(ControlConfig));
}

int saveMiscConfig(int type)
{
    char path[MAX_PATH_LENGTH];
    makeConfigPath(path, MISC_CONFIG_NAME, type);

    char parent_path[MAX_PATH_LENGTH];
    makeBaseDirectory(parent_path, path, MAX_PATH_LENGTH);
    createFolder(parent_path);

    return WriteFile(path, &misc_config, sizeof(MiscConfig));
}

int setDefaultCoreConfig()
{
    if (!core_entries)
        return -1;

    int i, j;
    for (i = 0; i < getCoreOptionsNum(); i++)
    {
        CoreOptionValue *option = (CoreOptionValue *)core_entries[i].option;
        char **values = option->values;
        char *default_value = option->default_value;
        if (!values || !default_value)
            continue;

        j = 0;
        while (values[j])
        {
            if (strcmp(values[j], default_value) == 0)
            {
                option->pos = j;
                break;
            }
            j++;
        }
    }

    return 0;
}

int loadCoreConfig(int type)
{
    if (!core_entries)
        return -1;

    char path[MAX_PATH_LENGTH];
    makeConfigPath(path, CORE_CONFIG_NAME, type);

    return readConfig(path, core_entries, getCoreOptionsNum());
}

int saveCoreConfig(int type)
{
    if (!core_entries)
        return -1;

    char path[MAX_PATH_LENGTH];
    makeConfigPath(path, CORE_CONFIG_NAME, type);

    char parent_path[MAX_PATH_LENGTH];
    makeBaseDirectory(parent_path, path, MAX_PATH_LENGTH);
    createFolder(parent_path);

    return writeConfig(path, core_entries, getCoreOptionsNum());
}
